
import cv2
import numpy as np

A = (60, 90)  # A选项横坐标范围
B = (130, 160)  # B选项横坐标范围
C = (200, 230)  # C选项横坐标范围
D = (270, 300)  # D选项横坐标范围
# 每一道题的纵坐标范围
questions_ordinate = [[15, 30], [60, 75], [105, 120], [145, 160], [195, 210], [280, 295], [324, 335], [365, 380],
                      [410, 425], [455, 465]]
result = {}  # 定义一个空字典，用于保存机读卡上填涂的答案

# 获取矩形图案的平面透视图
def perspective(img):
    w, h = 320, 480  # 俯视图的宽高
    tmp = cv2.GaussianBlur(img, (5, 5), 0)  # 高斯滤波
    tmp = cv2.Canny(tmp, 50, 120)  # 变为二值边缘图像
    tmp = cv2.morphologyEx(tmp, cv2.MORPH_CLOSE, (15, 15), iterations=2)  # 闭运算，保证边缘闭合
    res = cv2.resize(tmp, None, fx=0.35, fy=0.35)  # 缩放执行闭运算后的图像
    cv2.imshow("1", res)  # 窗口显示缩放后的、机读卡的边缘信息图像
    contours, _ = cv2.findContours(tmp, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 检测轮廓
    for c in contours:  # 遍历所有轮廓
        area = cv2.contourArea(c)  # 计算轮廓面积
        if area > 10000:  # 只处理面积廓大于10000的轮廓
            length = cv2.arcLength(c, True)  # 获取轮廓周长
            approx = cv2.approxPolyDP(c, 0.1 * length, True)  # 计算出轮廓的端点
            pts1 = np.float32(approx)  # 轮廓四个端点的坐标
            pts2 = np.float32([[0, 0], [0, h], [w, h], [w, 0]])  # 平面透视图对应的四个端点坐标
            M = cv2.getPerspectiveTransform(pts1, pts2)  # 创建透视图M矩阵
            tmp = cv2.warpPerspective(img, M, (w, h))  # 根据M矩阵做透视变换
    return tmp


# 根据横坐标判断选项
def get_result(x):
    if A[0] <= x <= A[1]:
        return "A"
    if B[0] <= x <= B[1]:
        return "B"
    if C[0] <= x <= C[1]:
        return "C"
    if D[0] <= x <= D[1]:
        return "D"


# 根据纵坐标判断题号
def get_question(y):
    for i in range(0, len(questions_ordinate)):  # 遍历10道题的纵坐标的取值范围
        y_bottom, y_up = questions_ordinate[i]  # 获取每一个题目的纵坐标范围
        if y_bottom <= y <= y_up:  # 如果在该题目坐标范围内
            return i + 1  # 返回题号


img = cv2.imread("answer.jpg")  # 机读答题卡的照片
tmp = perspective(img)  # 获取答题卡正面透视图
cv2.imshow("2", tmp)  # 窗口显示机读卡的平面透视图

tmp = cv2.cvtColor(tmp, cv2.COLOR_BGR2GRAY)  # 转为灰度图
_, tmp = cv2.threshold(tmp, 150, 255, cv2.THRESH_BINARY)  # 二值化阈值处理
cv2.imshow("3", tmp)  # 窗口显示对平面透视图进行二值化阈值处理的结果
k = np.ones((3, 3), np.uint8)  # 形态学操作所用的核
tmp = cv2.morphologyEx(tmp, cv2.MORPH_CLOSE, k, iterations=2)  # 对图像做闭运算，迭代2次
cv2.imshow("4", tmp)  # 窗口显示对二值化阈值处理后的图像进行闭运算后的结果
cv2.waitKey()
cv2.destroyAllWindows()
counr, _ = cv2.findContours(tmp, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)  # 检测所有轮廓
for c in counr:  # 遍历所有轮廓
    if 200 < cv2.contourArea(c) < 500:  # 至对符合涂卡面积的轮廓进行操作
        M = cv2.moments(c)  # 获取轮廓的矩
        x_center = int(M['m10'] / M['m00'])  # 轮廓重心的横坐标
        y_center = int(M['m01'] / M['m00'])  # 轮廓重心的纵坐标
        questio_num = get_question(y_center)  # 判断涂卡的位置属于哪个题目
        res = get_result(x_center)  # 判断涂卡位置属于哪个选项
        result[questio_num] = res  # 记录答题者在改题目下的选项

for key in result.keys():  # 遍历答题者的答案
    print("第", str(key), "题：", result[key])
